﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Net;
using System.CodeDom;
using System.CodeDom.Compiler;

namespace WebPALTT.Lib
{
    /// <summary>
    /// This class stores information about the scenario to be run.
    /// The class also has information about the conditions used to run the scenario,
    /// and a few runtime objects related to the scenario.
    /// 
    /// The Scenario object is the one passed in to the any custom scripts
    /// and is the starting point of accessing information about the scenario being run.    /// 
    /// </summary>
    [Serializable]
    public class Scenario : ISerializable
    {
        public Guid mKey;
        public string Name;
        public string Description;
        protected ActionItemCollection mActions;
        protected RunConditions mRunConditions;
        protected List<Cookie> mCookies = new List<Cookie> { }; // doesn't get serialized - only used for runtime.
        protected List<string> mCompileParameters;
        protected List<string> mUsingStatements;
        /// <summary>
        /// The compile parameters for the scenario.
        /// </summary>
        public List<string> CompileParameters { get { return mCompileParameters; } }
        /// <summary>
        /// The using statements for the script code.
        /// </summary>
        public List<string> UsingStatements { get { return mUsingStatements; } }
        
        /// <summary>
        /// Gets the collection of actions assigned to the scenario.
        /// </summary>
        public ActionItemCollection Actions { get { return mActions; } }

        public Guid Key { get { return mKey; } }

        /// <summary>
        /// Gets or sets the conditions used to run the scenario.
        /// </summary>
        public RunConditions RunConditions { get { return mRunConditions; } set { mRunConditions = value; } }

        /// <summary>
        /// Gets the collection of cookies for this scenario. Only used at runtime. 
        /// The cookies are not serialized if the object is save as a result or project.
        /// </summary>
        public List<Cookie> Cookies { get { return mCookies; } }

        public Scenario() : this("", "") { }

        public Scenario(string name, string desc)
        {
            this.Name = name;
            this.Description = desc;
            this.mActions = new ActionItemCollection();
            this.mRunConditions = null;
            this.mKey = Guid.NewGuid();
            this.mCompileParameters = new List<string> { };
            this.mUsingStatements = new List<string> { "System", "System.Collections.Generic", "System.Linq", "System.Text", "System.Net", "WebPALTT.Lib" };
        }

        #region Serialization

        public Scenario(SerializationInfo info, StreamingContext ctxt)
        {
            this.Name = (string)info.GetValue("Name", typeof(string));
            this.Description = (string)info.GetValue("Description", typeof(string));
            this.mActions = (ActionItemCollection)info.GetValue("Actions", typeof(ActionItemCollection));
            this.mRunConditions = (RunConditions)info.GetValue("RunConditions", typeof(RunConditions));
            try
            { this.mKey = (Guid)info.GetValue("Key", typeof(Guid)); }
            catch (SerializationException)
            { this.mKey = Guid.NewGuid(); }
            try
            { this.mCompileParameters = (List<string>)info.GetValue("CompileParameters", typeof(List<string>)); }
            catch (SerializationException)
            { this.mCompileParameters = new List<string> { }; }
            try
            { this.mUsingStatements = (List<string>)info.GetValue("UsingStatements", typeof(List<string>)); }
            catch (SerializationException)
            { this.mUsingStatements = new List<string> { "System", "System.Collections.Generic", "System.Linq", "System.Text", "System.Net", "WebPALTT.Lib" }; }
        }

        public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            info.AddValue("Name", Name);
            info.AddValue("Description", Description);
            info.AddValue("Actions", mActions);
            info.AddValue("RunConditions", mRunConditions);
            info.AddValue("Key", mKey);
            info.AddValue("CompileParameters", mCompileParameters);
            info.AddValue("UsingStatements", mUsingStatements);
        }

        #endregion

        public string GetScriptCode(string classname)
        {
            //List<string> usingStatements = new List<string> { };
            string code = "namespace Script { public class " + classname + " {\r\n";
            ActionItem[] actions = mActions.FindAll(
                delegate(ActionItem action)
                {
                    if(action.GetType().IsSubclassOf(typeof(ScriptItem)))
                        return true;
                    return false;
                }, true);
            foreach (ActionItem action in actions)                
                    code += ((ScriptItem)action).ScriptCode;
            code += "}}";

            string codetop = "";
            foreach (string str in mUsingStatements)
                codetop += "using " + str + ";\r\n";

            return codetop + "\r\n" + code;
        }

        public string[] Validate()
        {
            List<string> errors = new List<string> { }; 
            
            for (int i = 0; i < mActions.Count; i++)
            {
                for (int j = i + 1; j < mActions.Count; j++)
                    if (mActions[i].Name.ToLower().Equals(mActions[j].Name.ToLower()))
                        errors.Add("Error: Duplicate name exists [" + mActions[i].Name + "].");
                errors.AddRange(mActions[i].Validate());
            }
            
            List<string> assemblyNames = new List<string> { "System.dll", "mscorlib.dll", "System.Core.dll", "WebPALTT.Lib.dll" };
            string code;
            foreach (string str in this.mCompileParameters)
                    if (!assemblyNames.Contains(str))
                        assemblyNames.Add(str);
            code = this.GetScriptCode("Scenario_Test");            

            CodeDomProvider provider = new Microsoft.CSharp.CSharpCodeProvider();
            CompilerParameters mCompileParams = new CompilerParameters(assemblyNames.ToArray());
            mCompileParams.GenerateInMemory = true;
            mCompileParams.CompilerOptions = "/target:library /optimize";

            CompilerResults results = provider.CompileAssemblyFromSource(mCompileParams, new string[] {code});
            if (results.Errors.HasErrors)
            {
                foreach (CompilerError err in results.Errors)
                    errors.Add("Compile Error : " + err.ErrorText);
            }
            return errors.ToArray();
        }

        /// <summary>
        /// Returns the first cookie with the same name or null if the name doesn't match.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Cookie GetCookie(string name)
        {
            for (int i = 0; i < mCookies.Count; i++)
                if (mCookies[i].Name.ToUpper().Equals(name.ToUpper()))
                    return mCookies[i];
            return null;
        }

        /// <summary>
        /// Returns the index of the first cookie with the same name or -1 if the name doesn't match.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public int GetCookieIndex(string name)
        {
            for (int i = 0; i < mCookies.Count; i++)
                if (mCookies[i].Name.ToUpper().Equals(name.ToUpper()))
                    return i;
            return -1;
        }

        /// <summary>
        /// Updates an existing cookie with the same name or creates a new cookie if the name doesn't match.
        /// </summary>
        /// <param name="cookie"></param>
        public void SetCookie(Cookie cookie)
        {            
            if (cookie == null)
                throw new ArgumentNullException("cookie is null");
            lock (cookie)
            {
                int i = GetCookieIndex(cookie.Name);
                if (i == -1)
                    mCookies.Add(cookie);
                else
                    mCookies[i] = cookie;
            }
        }
    }

    [Serializable]
    public class RunConditions
    {
        public int VirtualUsers;
        public RunMode RunMode;

        public int StartingUsers;
        public int RampUpUsers;
        public int RampUpTimeSpan;
        public bool StopOnUserCount;
        public bool StopOnMaxTime;
        public int MaxTime;

        public RunConditions()
        {
            VirtualUsers = 1;
            RunMode = WebPALTT.Lib.RunMode.Concurrent;
            StartingUsers = 1;
            RampUpUsers = 0;
            RampUpTimeSpan = 1;
            StopOnUserCount = true;
            StopOnMaxTime = false;
            MaxTime = 0;
        }

        public RunConditions(SerializationInfo info, StreamingContext ctxt)
        {
            VirtualUsers = (int)info.GetValue("VitualUsers", typeof(int));
            RunMode = (RunMode)info.GetValue("RunMode", typeof(RunMode));

            StartingUsers = (int)info.GetValue("StartingUsers", typeof(int));
            RampUpUsers = (int)info.GetValue("RampUpUsers", typeof(int));
            RampUpTimeSpan = (int)info.GetValue("RampUpTimeSpan", typeof(int));
            StopOnUserCount = (bool)info.GetValue("StopOnUserCount", typeof(bool));
            StopOnMaxTime = (bool)info.GetValue("StopOnMaxTime", typeof(bool));
            MaxTime = (int)info.GetValue("MaxTime", typeof(int));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            info.AddValue("VitualUsers", VirtualUsers);
            info.AddValue("RunMode", RunMode);

            info.AddValue("StartingUsers", StartingUsers);
            info.AddValue("RampUpUsers", RampUpUsers);
            info.AddValue("RampUpTimeSpan", RampUpTimeSpan);
            info.AddValue("StopOnUserCount", StopOnUserCount);
            info.AddValue("StopOnMaxTime", StopOnMaxTime);
            info.AddValue("MaxTime", MaxTime);
        }

        public int TotalUserCount
        {
            get
            {
                if (RunMode == Lib.RunMode.RampUp)
                    if (StopOnUserCount)
                        return VirtualUsers;
                    else if (StopOnMaxTime)
                        return (((MaxTime * 60) / (RampUpTimeSpan > 0 ? RampUpTimeSpan : 1)) * RampUpUsers) + StartingUsers;
                    else
                        return -1;
                else
                    return VirtualUsers;
            }
        }

    }
}
